package cn.dansj.common.utils.spring.context.spel;

import cn.dansj.common.utils.json.JSONUtils;
import cn.dansj.common.utils.json.JSONObject;
import cn.dansj.common.utils.spring.SpringGlobalEnv;
import org.springframework.context.expression.MapAccessor;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.KotlinDetector;
import org.springframework.util.ObjectUtils;

import java.lang.reflect.Method;
import java.util.Arrays;

public class MethodEvaluationContext extends MethodBasedEvaluationContext {
    private final Method method;
    private final Object[] arguments;

    public MethodEvaluationContext(Object rootObject, Method method, Object[] arguments) throws NoSuchMethodException {
        super(rootObject, method, arguments, SpringGlobalEnv.NAME_DISCOVERER);
        this.method = method;
        this.arguments = (KotlinDetector.isSuspendingFunction(method) ? Arrays.copyOf(arguments, arguments.length - 1) : arguments);
        initContext();
    }

    public void initContext() throws NoSuchMethodException {
        setVariable("method", method.getDeclaringClass().getName() + "." + method.getName());
        addPropertyAccessor(new MapAccessor());
        registerFunction("json", MethodEvaluationContext.class.getDeclaredMethod("getJSON", Object.class, String.class));
    }

    private static Object getJSON(Object object, String key) {
        try {
            final JSONObject map = JSONUtils.from(object).toJSONObject();
            return map.containsKey(key) ? map.get(key) : map.at(key);
        } catch (Exception exception) {
            return null;
        }
    }

    /**
     * Load the param information only when needed.
     */
    public void lazyLoadArguments() {
        // Shortcut if no args need to be loaded
        if (ObjectUtils.isEmpty(this.arguments)) {
            return;
        }

        // Expose indexed variables as well as parameter names (if discoverable)
        String[] paramNames = SpringGlobalEnv.NAME_DISCOVERER.getParameterNames(this.method);
        int paramCount = (paramNames != null ? paramNames.length : this.method.getParameterCount());
        int argsCount = this.arguments.length;
        for (int i = 0; i < paramCount; i++) {
            Object value = null;
            if (argsCount > paramCount && i == paramCount - 1) {
                // Expose remaining arguments as vararg array for last parameter
                value = Arrays.copyOfRange(this.arguments, i, argsCount);
            } else if (argsCount > i) {
                // Actual argument found - otherwise left as null
                value = this.arguments[i];
            }
            setVariable("arg" + i, value);
            if (paramNames != null && paramNames[i] != null) {
                setVariable(paramNames[i], value);
            }
        }
    }
}